summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCharles Lombardo <clombardo169@gmail.com>2023-03-08 21:38:16 +0100
committerbunnei <bunneidev@gmail.com>2023-06-03 09:05:38 +0200
commit4ce86a526c038ddea0694b299338ec1e2699d38b (patch)
treeb2d95f1dd9ed301423e71b51d8d87446c6c8ae78
parentandroid: Convert Game to Kotlin (diff)
downloadyuzu-4ce86a526c038ddea0694b299338ec1e2699d38b.tar
yuzu-4ce86a526c038ddea0694b299338ec1e2699d38b.tar.gz
yuzu-4ce86a526c038ddea0694b299338ec1e2699d38b.tar.bz2
yuzu-4ce86a526c038ddea0694b299338ec1e2699d38b.tar.lz
yuzu-4ce86a526c038ddea0694b299338ec1e2699d38b.tar.xz
yuzu-4ce86a526c038ddea0694b299338ec1e2699d38b.tar.zst
yuzu-4ce86a526c038ddea0694b299338ec1e2699d38b.zip
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameDatabase.java275
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameDatabase.kt260
2 files changed, 260 insertions, 275 deletions
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameDatabase.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameDatabase.java
deleted file mode 100644
index a10ac6ff2..000000000
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameDatabase.java
+++ /dev/null
@@ -1,275 +0,0 @@
-package org.yuzu.yuzu_emu.model;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.net.Uri;
-
-import org.yuzu.yuzu_emu.NativeLibrary;
-import org.yuzu.yuzu_emu.utils.FileUtil;
-import org.yuzu.yuzu_emu.utils.Log;
-
-import java.io.File;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
-
-import rx.Observable;
-
-/**
- * A helper class that provides several utilities simplifying interaction with
- * the SQLite database.
- */
-public final class GameDatabase extends SQLiteOpenHelper {
- public static final int COLUMN_DB_ID = 0;
- public static final int GAME_COLUMN_PATH = 1;
- public static final int GAME_COLUMN_TITLE = 2;
- public static final int GAME_COLUMN_DESCRIPTION = 3;
- public static final int GAME_COLUMN_REGIONS = 4;
- public static final int GAME_COLUMN_GAME_ID = 5;
- public static final int GAME_COLUMN_CAPTION = 6;
- public static final int FOLDER_COLUMN_PATH = 1;
- public static final String KEY_DB_ID = "_id";
- public static final String KEY_GAME_PATH = "path";
- public static final String KEY_GAME_TITLE = "title";
- public static final String KEY_GAME_DESCRIPTION = "description";
- public static final String KEY_GAME_REGIONS = "regions";
- public static final String KEY_GAME_ID = "game_id";
- public static final String KEY_GAME_COMPANY = "company";
- public static final String KEY_FOLDER_PATH = "path";
- public static final String TABLE_NAME_FOLDERS = "folders";
- public static final String TABLE_NAME_GAMES = "games";
- private static final int DB_VERSION = 2;
- private static final String TYPE_PRIMARY = " INTEGER PRIMARY KEY";
- private static final String TYPE_INTEGER = " INTEGER";
- private static final String TYPE_STRING = " TEXT";
-
- private static final String CONSTRAINT_UNIQUE = " UNIQUE";
-
- private static final String SEPARATOR = ", ";
-
- private static final String SQL_CREATE_GAMES = "CREATE TABLE " + TABLE_NAME_GAMES + "("
- + KEY_DB_ID + TYPE_PRIMARY + SEPARATOR
- + KEY_GAME_PATH + TYPE_STRING + SEPARATOR
- + KEY_GAME_TITLE + TYPE_STRING + SEPARATOR
- + KEY_GAME_DESCRIPTION + TYPE_STRING + SEPARATOR
- + KEY_GAME_REGIONS + TYPE_STRING + SEPARATOR
- + KEY_GAME_ID + TYPE_STRING + SEPARATOR
- + KEY_GAME_COMPANY + TYPE_STRING + ")";
-
- private static final String SQL_CREATE_FOLDERS = "CREATE TABLE " + TABLE_NAME_FOLDERS + "("
- + KEY_DB_ID + TYPE_PRIMARY + SEPARATOR
- + KEY_FOLDER_PATH + TYPE_STRING + CONSTRAINT_UNIQUE + ")";
-
- private static final String SQL_DELETE_FOLDERS = "DROP TABLE IF EXISTS " + TABLE_NAME_FOLDERS;
- private static final String SQL_DELETE_GAMES = "DROP TABLE IF EXISTS " + TABLE_NAME_GAMES;
- private final Context context;
-
- public GameDatabase(Context context) {
- // Superclass constructor builds a database or uses an existing one.
- super(context, "games.db", null, DB_VERSION);
- this.context = context;
- }
-
- @Override
- public void onCreate(SQLiteDatabase database) {
- Log.debug("[GameDatabase] GameDatabase - Creating database...");
-
- execSqlAndLog(database, SQL_CREATE_GAMES);
- execSqlAndLog(database, SQL_CREATE_FOLDERS);
- }
-
- @Override
- public void onDowngrade(SQLiteDatabase database, int oldVersion, int newVersion) {
- Log.verbose("[GameDatabase] Downgrades not supporting, clearing databases..");
- execSqlAndLog(database, SQL_DELETE_FOLDERS);
- execSqlAndLog(database, SQL_CREATE_FOLDERS);
-
- execSqlAndLog(database, SQL_DELETE_GAMES);
- execSqlAndLog(database, SQL_CREATE_GAMES);
- }
-
- @Override
- public void onUpgrade(SQLiteDatabase database, int oldVersion, int newVersion) {
- Log.info("[GameDatabase] Upgrading database from schema version " + oldVersion + " to " +
- newVersion);
-
- // Delete all the games
- execSqlAndLog(database, SQL_DELETE_GAMES);
- execSqlAndLog(database, SQL_CREATE_GAMES);
- }
-
- public void resetDatabase(SQLiteDatabase database) {
- execSqlAndLog(database, SQL_DELETE_FOLDERS);
- execSqlAndLog(database, SQL_CREATE_FOLDERS);
-
- execSqlAndLog(database, SQL_DELETE_GAMES);
- execSqlAndLog(database, SQL_CREATE_GAMES);
- }
-
- public void scanLibrary(SQLiteDatabase database) {
- // Before scanning known folders, go through the game table and remove any entries for which the file itself is missing.
- Cursor fileCursor = database.query(TABLE_NAME_GAMES,
- null, // Get all columns.
- null, // Get all rows.
- null,
- null, // No grouping.
- null,
- null); // Order of games is irrelevant.
-
- // Possibly overly defensive, but ensures that moveToNext() does not skip a row.
- fileCursor.moveToPosition(-1);
-
- while (fileCursor.moveToNext()) {
- String gamePath = fileCursor.getString(GAME_COLUMN_PATH);
- File game = new File(gamePath);
-
- if (!game.exists()) {
- database.delete(TABLE_NAME_GAMES,
- KEY_DB_ID + " = ?",
- new String[]{Long.toString(fileCursor.getLong(COLUMN_DB_ID))});
- }
- }
-
- // Get a cursor listing all the folders the user has added to the library.
- Cursor folderCursor = database.query(TABLE_NAME_FOLDERS,
- null, // Get all columns.
- null, // Get all rows.
- null,
- null, // No grouping.
- null,
- null); // Order of folders is irrelevant.
-
- Set<String> allowedExtensions = new HashSet<String>(Arrays.asList(
- ".xci", ".nsp", ".nca", ".nro"));
-
- // Possibly overly defensive, but ensures that moveToNext() does not skip a row.
- folderCursor.moveToPosition(-1);
-
- // Iterate through all results of the DB query (i.e. all folders in the library.)
- while (folderCursor.moveToNext()) {
- String folderPath = folderCursor.getString(FOLDER_COLUMN_PATH);
-
- Uri folderUri = Uri.parse(folderPath);
- // If the folder is empty because it no longer exists, remove it from the library.
- if (FileUtil.listFiles(context, folderUri).length == 0) {
- Log.error(
- "[GameDatabase] Folder no longer exists. Removing from the library: " + folderPath);
- database.delete(TABLE_NAME_FOLDERS,
- KEY_DB_ID + " = ?",
- new String[]{Long.toString(folderCursor.getLong(COLUMN_DB_ID))});
- }
-
- this.addGamesRecursive(database, folderUri, allowedExtensions, 3);
- }
-
- fileCursor.close();
- folderCursor.close();
-
- database.close();
- }
-
- private void addGamesRecursive(SQLiteDatabase database, Uri parent, Set<String> allowedExtensions, int depth) {
- if (depth <= 0) {
- return;
- }
-
- // Ensure keys are loaded so that ROM metadata can be decrypted.
- NativeLibrary.ReloadKeys();
-
- MinimalDocumentFile[] children = FileUtil.listFiles(context, parent);
- for (MinimalDocumentFile file : children) {
- if (file.isDirectory()) {
- Set<String> newExtensions = new HashSet<>(Arrays.asList(
- ".xci", ".nsp", ".nca", ".nro"));
- this.addGamesRecursive(database, file.getUri(), newExtensions, depth - 1);
- } else {
- String filename = file.getUri().toString();
-
- int extensionStart = filename.lastIndexOf('.');
- if (extensionStart > 0) {
- String fileExtension = filename.substring(extensionStart);
-
- // Check that the file has an extension we care about before trying to read out of it.
- if (allowedExtensions.contains(fileExtension.toLowerCase())) {
- attemptToAddGame(database, filename);
- }
- }
- }
- }
- }
-
- private static void attemptToAddGame(SQLiteDatabase database, String filePath) {
- String name = NativeLibrary.GetTitle(filePath);
-
- // If the game's title field is empty, use the filename.
- if (name.isEmpty()) {
- name = filePath.substring(filePath.lastIndexOf("/") + 1);
- }
-
- String gameId = NativeLibrary.GetGameId(filePath);
-
- // If the game's ID field is empty, use the filename without extension.
- if (gameId.isEmpty()) {
- gameId = filePath.substring(filePath.lastIndexOf("/") + 1,
- filePath.lastIndexOf("."));
- }
-
- ContentValues game = Game.asContentValues(name,
- NativeLibrary.GetDescription(filePath).replace("\n", " "),
- NativeLibrary.GetRegions(filePath),
- filePath,
- gameId,
- NativeLibrary.GetCompany(filePath));
-
- // Try to update an existing game first.
- int rowsMatched = database.update(TABLE_NAME_GAMES, // Which table to update.
- game,
- // The values to fill the row with.
- KEY_GAME_ID + " = ?",
- // The WHERE clause used to find the right row.
- new String[]{game.getAsString(
- KEY_GAME_ID)}); // The ? in WHERE clause is replaced with this,
- // which is provided as an array because there
- // could potentially be more than one argument.
-
- // If update fails, insert a new game instead.
- if (rowsMatched == 0) {
- Log.verbose("[GameDatabase] Adding game: " + game.getAsString(KEY_GAME_TITLE));
- database.insert(TABLE_NAME_GAMES, null, game);
- } else {
- Log.verbose("[GameDatabase] Updated game: " + game.getAsString(KEY_GAME_TITLE));
- }
- }
-
- public Observable<Cursor> getGames() {
- return Observable.create(subscriber ->
- {
- Log.info("[GameDatabase] Reading games list...");
-
- SQLiteDatabase database = getReadableDatabase();
- Cursor resultCursor = database.query(
- TABLE_NAME_GAMES,
- null,
- null,
- null,
- null,
- null,
- KEY_GAME_TITLE + " ASC"
- );
-
- // Pass the result cursor to the consumer.
- subscriber.onNext(resultCursor);
-
- // Tell the consumer we're done; it will unsubscribe implicitly.
- subscriber.onCompleted();
- });
- }
-
- private void execSqlAndLog(SQLiteDatabase database, String sql) {
- Log.verbose("[GameDatabase] Executing SQL: " + sql);
- database.execSQL(sql);
- }
-}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameDatabase.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameDatabase.kt
new file mode 100644
index 000000000..52326ed0a
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GameDatabase.kt
@@ -0,0 +1,260 @@
+package org.yuzu.yuzu_emu.model
+
+import android.content.Context
+import android.database.Cursor
+import android.database.sqlite.SQLiteDatabase
+import android.database.sqlite.SQLiteOpenHelper
+import android.net.Uri
+import org.yuzu.yuzu_emu.NativeLibrary
+import org.yuzu.yuzu_emu.utils.FileUtil
+import org.yuzu.yuzu_emu.utils.Log
+import rx.Observable
+import rx.Subscriber
+import java.io.File
+import java.util.*
+
+/**
+ * A helper class that provides several utilities simplifying interaction with
+ * the SQLite database.
+ */
+class GameDatabase(private val context: Context) :
+ SQLiteOpenHelper(context, "games.db", null, DB_VERSION) {
+ override fun onCreate(database: SQLiteDatabase) {
+ Log.debug("[GameDatabase] GameDatabase - Creating database...")
+ execSqlAndLog(database, SQL_CREATE_GAMES)
+ execSqlAndLog(database, SQL_CREATE_FOLDERS)
+ }
+
+ override fun onDowngrade(database: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
+ Log.verbose("[GameDatabase] Downgrades not supporting, clearing databases..")
+ execSqlAndLog(database, SQL_DELETE_FOLDERS)
+ execSqlAndLog(database, SQL_CREATE_FOLDERS)
+ execSqlAndLog(database, SQL_DELETE_GAMES)
+ execSqlAndLog(database, SQL_CREATE_GAMES)
+ }
+
+ override fun onUpgrade(database: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
+ Log.info(
+ "[GameDatabase] Upgrading database from schema version $oldVersion to $newVersion"
+ )
+
+ // Delete all the games
+ execSqlAndLog(database, SQL_DELETE_GAMES)
+ execSqlAndLog(database, SQL_CREATE_GAMES)
+ }
+
+ fun resetDatabase(database: SQLiteDatabase) {
+ execSqlAndLog(database, SQL_DELETE_FOLDERS)
+ execSqlAndLog(database, SQL_CREATE_FOLDERS)
+ execSqlAndLog(database, SQL_DELETE_GAMES)
+ execSqlAndLog(database, SQL_CREATE_GAMES)
+ }
+
+ fun scanLibrary(database: SQLiteDatabase) {
+ // Before scanning known folders, go through the game table and remove any entries for which the file itself is missing.
+ val fileCursor = database.query(
+ TABLE_NAME_GAMES,
+ null, // Get all columns.
+ null, // Get all rows.
+ null,
+ null, // No grouping.
+ null,
+ null
+ ) // Order of games is irrelevant.
+
+ // Possibly overly defensive, but ensures that moveToNext() does not skip a row.
+ fileCursor.moveToPosition(-1)
+ while (fileCursor.moveToNext()) {
+ val gamePath = fileCursor.getString(GAME_COLUMN_PATH)
+ val game = File(gamePath)
+ if (!game.exists()) {
+ database.delete(
+ TABLE_NAME_GAMES,
+ "$KEY_DB_ID = ?",
+ arrayOf(fileCursor.getLong(COLUMN_DB_ID).toString())
+ )
+ }
+ }
+
+ // Get a cursor listing all the folders the user has added to the library.
+ val folderCursor = database.query(
+ TABLE_NAME_FOLDERS,
+ null, // Get all columns.
+ null, // Get all rows.
+ null,
+ null, // No grouping.
+ null,
+ null
+ ) // Order of folders is irrelevant.
+
+
+ // Possibly overly defensive, but ensures that moveToNext() does not skip a row.
+ folderCursor.moveToPosition(-1)
+
+ // Iterate through all results of the DB query (i.e. all folders in the library.)
+ while (folderCursor.moveToNext()) {
+ val folderPath = folderCursor.getString(FOLDER_COLUMN_PATH)
+ val folderUri = Uri.parse(folderPath)
+ // If the folder is empty because it no longer exists, remove it from the library.
+ if (FileUtil.listFiles(context, folderUri).isEmpty()) {
+ Log.error(
+ "[GameDatabase] Folder no longer exists. Removing from the library: $folderPath"
+ )
+ database.delete(
+ TABLE_NAME_FOLDERS,
+ "$KEY_DB_ID = ?",
+ arrayOf(folderCursor.getLong(COLUMN_DB_ID).toString())
+ )
+ }
+ addGamesRecursive(database, folderUri, Game.extensions, 3)
+ }
+ fileCursor.close()
+ folderCursor.close()
+ database.close()
+ }
+
+ private fun addGamesRecursive(
+ database: SQLiteDatabase,
+ parent: Uri,
+ allowedExtensions: Set<String>,
+ depth: Int
+ ) {
+ if (depth <= 0)
+ return
+
+ // Ensure keys are loaded so that ROM metadata can be decrypted.
+ NativeLibrary.ReloadKeys()
+ val children = FileUtil.listFiles(context, parent)
+ for (file in children) {
+ if (file.isDirectory) {
+ addGamesRecursive(database, file.uri, Game.extensions, depth - 1)
+ } else {
+ val filename = file.uri.toString()
+ val extensionStart = filename.lastIndexOf('.')
+ if (extensionStart > 0) {
+ val fileExtension = filename.substring(extensionStart)
+
+ // Check that the file has an extension we care about before trying to read out of it.
+ if (allowedExtensions.contains(fileExtension.lowercase(Locale.getDefault()))) {
+ attemptToAddGame(database, filename)
+ }
+ }
+ }
+ }
+ }
+ // Pass the result cursor to the consumer.
+
+ // Tell the consumer we're done; it will unsubscribe implicitly.
+ val games: Observable<Cursor?>
+ get() = Observable.create { subscriber: Subscriber<in Cursor?> ->
+ Log.info("[GameDatabase] Reading games list...")
+ val database = readableDatabase
+ val resultCursor = database.query(
+ TABLE_NAME_GAMES,
+ null,
+ null,
+ null,
+ null,
+ null,
+ "$KEY_GAME_TITLE ASC"
+ )
+
+ // Pass the result cursor to the consumer.
+ subscriber.onNext(resultCursor)
+
+ // Tell the consumer we're done; it will unsubscribe implicitly.
+ subscriber.onCompleted()
+ }
+
+ private fun execSqlAndLog(database: SQLiteDatabase, sql: String) {
+ Log.verbose("[GameDatabase] Executing SQL: $sql")
+ database.execSQL(sql)
+ }
+
+ companion object {
+ const val COLUMN_DB_ID = 0
+ const val GAME_COLUMN_PATH = 1
+ const val GAME_COLUMN_TITLE = 2
+ const val GAME_COLUMN_DESCRIPTION = 3
+ const val GAME_COLUMN_REGIONS = 4
+ const val GAME_COLUMN_GAME_ID = 5
+ const val GAME_COLUMN_CAPTION = 6
+ const val FOLDER_COLUMN_PATH = 1
+ const val KEY_DB_ID = "_id"
+ const val KEY_GAME_PATH = "path"
+ const val KEY_GAME_TITLE = "title"
+ const val KEY_GAME_DESCRIPTION = "description"
+ const val KEY_GAME_REGIONS = "regions"
+ const val KEY_GAME_ID = "game_id"
+ const val KEY_GAME_COMPANY = "company"
+ const val KEY_FOLDER_PATH = "path"
+ const val TABLE_NAME_FOLDERS = "folders"
+ const val TABLE_NAME_GAMES = "games"
+ private const val DB_VERSION = 2
+ private const val TYPE_PRIMARY = " INTEGER PRIMARY KEY"
+ private const val TYPE_INTEGER = " INTEGER"
+ private const val TYPE_STRING = " TEXT"
+ private const val CONSTRAINT_UNIQUE = " UNIQUE"
+ private const val SEPARATOR = ", "
+ private const val SQL_CREATE_GAMES = ("CREATE TABLE " + TABLE_NAME_GAMES + "("
+ + KEY_DB_ID + TYPE_PRIMARY + SEPARATOR
+ + KEY_GAME_PATH + TYPE_STRING + SEPARATOR
+ + KEY_GAME_TITLE + TYPE_STRING + SEPARATOR
+ + KEY_GAME_DESCRIPTION + TYPE_STRING + SEPARATOR
+ + KEY_GAME_REGIONS + TYPE_STRING + SEPARATOR
+ + KEY_GAME_ID + TYPE_STRING + SEPARATOR
+ + KEY_GAME_COMPANY + TYPE_STRING + ")")
+ private const val SQL_CREATE_FOLDERS = ("CREATE TABLE " + TABLE_NAME_FOLDERS + "("
+ + KEY_DB_ID + TYPE_PRIMARY + SEPARATOR
+ + KEY_FOLDER_PATH + TYPE_STRING + CONSTRAINT_UNIQUE + ")")
+ private const val SQL_DELETE_FOLDERS = "DROP TABLE IF EXISTS $TABLE_NAME_FOLDERS"
+ private const val SQL_DELETE_GAMES = "DROP TABLE IF EXISTS $TABLE_NAME_GAMES"
+ private fun attemptToAddGame(database: SQLiteDatabase, filePath: String) {
+ var name = NativeLibrary.GetTitle(filePath)
+
+ // If the game's title field is empty, use the filename.
+ if (name.isEmpty()) {
+ name = filePath.substring(filePath.lastIndexOf("/") + 1)
+ }
+ var gameId = NativeLibrary.GetGameId(filePath)
+
+ // If the game's ID field is empty, use the filename without extension.
+ if (gameId.isEmpty()) {
+ gameId = filePath.substring(
+ filePath.lastIndexOf("/") + 1,
+ filePath.lastIndexOf(".")
+ )
+ }
+ val game = Game.asContentValues(
+ name,
+ NativeLibrary.GetDescription(filePath).replace("\n", " "),
+ NativeLibrary.GetRegions(filePath),
+ filePath,
+ gameId,
+ NativeLibrary.GetCompany(filePath)
+ )
+
+ // Try to update an existing game first.
+ val rowsMatched = database.update(
+ TABLE_NAME_GAMES, // Which table to update.
+ game, // The values to fill the row with.
+ "$KEY_GAME_ID = ?", arrayOf(
+ game.getAsString(
+ KEY_GAME_ID
+ )
+ )
+ )
+ // The ? in WHERE clause is replaced with this,
+ // which is provided as an array because there
+ // could potentially be more than one argument.
+
+ // If update fails, insert a new game instead.
+ if (rowsMatched == 0) {
+ Log.verbose("[GameDatabase] Adding game: " + game.getAsString(KEY_GAME_TITLE))
+ database.insert(TABLE_NAME_GAMES, null, game)
+ } else {
+ Log.verbose("[GameDatabase] Updated game: " + game.getAsString(KEY_GAME_TITLE))
+ }
+ }
+ }
+}